package com.boot2.core.scheduler;

import com.boot2.core.HlAssert;
import com.boot2.core.dao.i.SchedulerTaskDao;
import com.boot2.core.exception.BusinessException;
import com.boot2.core.exception.HlSchedulerException;
import com.boot2.core.model.SchedulerTask;
import lombok.extern.apachecommons.CommonsLog;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author zhangweilin
 * @Description:
 * @date 2018/6/7
 * 参考 http://qieyi28.iteye.com/blog/2232301
 */
//@Component  //todo 神奇，我在具体应用中已经用@Bean创建了，但是如果不加此，下面的init里面的service会为空，而加上此后，没有默认构造也不报异常，且此时的init里面service也不报空了
@CommonsLog
public class ScheduleManager {

    @Autowired(required = false)
    SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    SchedulerTaskDao schedulerTaskDao;
    @Autowired(required = false)
    AbstractSchedulerTaskRouter abstractSchedulerTaskRouter;
    /**
     * 并发的job对应的class,做为被调度任务的入口
     */
    private Class<?> concurrentJobClass;
    /**
     * 并发的job对应的class,做为被调度任务的入口
     */
    private Class<?> unConcurrentJobClass;

    @Autowired
    JobListener jobListener;

    public ScheduleManager() {

    }

    /**
     * 必须要在具体的应用中创建此bean,如果不是自己new的，而是由spring自己创建，当没有默认构造时，只能调用此构造，此时参数会从上下文中找，如果找不到，会抛异常
     *
     * @param concurrentJobClass
     * @param unConcurrentJobClass
     */
    public ScheduleManager(Class<?> concurrentJobClass, Class<?> unConcurrentJobClass) {
        this.concurrentJobClass = concurrentJobClass;
        this.unConcurrentJobClass = unConcurrentJobClass;
    }

    @PostConstruct
    public void init() {
        if (abstractSchedulerTaskRouter != null&&schedulerFactoryBean!=null) {
            abstractSchedulerTaskRouter.init();
        }
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return
     */
    public List<SchedulerTask> getAllTaskList() {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        List<String> codeList = null;
        try {
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            codeList = new ArrayList<>();
            for (JobKey jobKey : jobKeys) {
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                String code = (String) jobDetail.getJobDataMap().get(SchedulerJobDataMapConsant.code);
//                System.out.println("code = " + code);
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
//                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
//                    System.out.println("triggerState = " + triggerState);
                    codeList.add(code);
                }
            }
//            System.out.println("codeList = " + codeList);
            List<SchedulerTask> schedulerTaskList = schedulerTaskDao.findAllByCodeIn(codeList);
            return schedulerTaskList;
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new HlSchedulerException("获取所有job失败.", e);
        }

    }


    /**
     * 所有正在运行的job
     *
     * @return
     */
    public List<SchedulerTask> getAllRunningTaskLList() {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        List<String> codeList = null;
        try {
            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
//        List<SchedulerTask> jobList = new ArrayList<SchedulerTask>(executingJobs.size());
            codeList = new ArrayList<>();
            for (JobExecutionContext executingJob : executingJobs) {
                JobDetail jobDetail = executingJob.getJobDetail();
                JobKey jobKey = jobDetail.getKey();
                Trigger trigger = executingJob.getTrigger();

                String code = (String) jobDetail.getJobDataMap().get(SchedulerJobDataMapConsant.code);
//                System.out.println("code = " + code);
                codeList.add(code);
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
//                System.out.println("triggerState = " + triggerState);
            }
            List<SchedulerTask> schedulerTaskList = schedulerTaskDao.findAllByCodeIn(codeList);
            return schedulerTaskList;
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new HlSchedulerException("获取正在执行的job失败.", e);
        }
    }

    /**
     * 暂停一个job
     *
     * @param taskId
     * @throws SchedulerException
     */
    public void pauseTask(Long taskId) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        log.info("暂停任务,taskId: "+taskId);
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = getJobKey(taskId);
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new HlSchedulerException("暂停job失败,taskId: " + taskId, e);
        }
    }

    /**
     * 删除一个job，即彻底停止一个job，它是从内存上下文中删除
     *
     * @param taskId
     * @throws SchedulerException
     */
    public void stopTask(Long taskId) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        log.info("停止任务,taskId: "+taskId);
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = getJobKey(taskId);
//            SchedulerTask schedulerTask = schedulerTaskDao.findOne(new SchedulerTask(taskId));
            SchedulerTask schedulerTask = schedulerTaskDao.getOne(taskId);
            HlAssert.notNull(schedulerTask,"找不到该定时任务");

            //如果是停止一个job,需要同步到库中去
            schedulerTask.setIsEnabled(false);
            schedulerTaskDao.saveAndFlush(schedulerTask);
            scheduler.deleteJob(jobKey);

        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new HlSchedulerException("删除或停止job失败,taskId: " + taskId, e);
        }
    }

    /**
     * 直接按业务code和id停止某个定时任务
     * @param code
     * @param bussiId
     */
    public void stopByCodeAndBussiId(String code,Long bussiId) {
        stopByCodeAndBussiId(code,bussiId+"");
    }

    /**
     * 直接按业务code和id停止某个定时任务
     * @param code
     * @param bussiId
     */
    public void stopByCodeAndBussiId(String code,Integer bussiId) {
        stopByCodeAndBussiId(code,bussiId+"");
    }

    /**
     * 直接按业务code和id停止某个定时任务
     * @param code
     * @param bussiId
     */
    public void stopByCodeAndBussiId(String code,String bussiId) {
        log.info("直接按业务code和id停止某个定时任务,code: "+code+" , bussiId: "+bussiId);
        SchedulerTask schedulerTask = schedulerTaskDao.findByCodeAndBussinessId(code, bussiId);
        if (null != schedulerTask) {
            stopTask(schedulerTask.getId());
        }
    }

    /**
     * 更新一个调度规则
     *
     * @param taskId
     * @throws SchedulerException
     */
    public void updateTaskCron(Long taskId, String expression) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        log.info("更新一个调度规则,taskId: "+taskId+", expression: "+expression);
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
//            SchedulerTask schedulerTask = schedulerTaskDao.findOne(new SchedulerTask(taskId));
            SchedulerTask schedulerTask = schedulerTaskDao.getOne(taskId);
            HlAssert.notNull(schedulerTask,"找不到该定时任务");
            schedulerTask.setCronExpression(expression);
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey(schedulerTask.getBussinessId() + "", schedulerTask.getCode());
                CronTrigger trigger = null;
                trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedulerTask.getCronExpression());
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
                scheduler.rescheduleJob(triggerKey, trigger);
            } catch (SchedulerException e1) {
                e1.printStackTrace();
                throw new HlSchedulerException("更新一个调度规则失败,taskId: " + taskId, e1);
            }

            //能触发才被认为是合法的表达式,才入库更新
            schedulerTaskDao.saveAndFlush(schedulerTask);
        } catch (Exception e) {
            e.printStackTrace();
            throw new HlSchedulerException("更新一个调度规则失败,taskId: " + taskId, e);
        }
    }

    /**
     * 获取
     * 当前task在quartz中的状态
     * NONE, NORMAL, PAUSED, COMPLETE, ERROR, BLOCKED
     *
     * @param schedulerTask
     * @return
     */

    public Trigger.TriggerState getTaskStatus(SchedulerTask schedulerTask) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        TriggerKey triggerKey = TriggerKey.triggerKey(schedulerTask.getBussinessId() + "", schedulerTask.getCode());
        try {
            Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
            return triggerState;
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new BusinessException("获取task状态失败", e);
        }
    }


    /**
     * 调度或恢复一个任务调度
     *
     * @param taskId
     */
    public void scheduleOrResumeATask(Long taskId) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        log.info("调度或恢复一个任务调度,taskId: "+taskId);
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            JobKey jobKey = getJobKey(taskId);
            SchedulerTask schedulerTask = schedulerTaskDao.getOne(taskId);
            HlAssert.notNull(schedulerTask,"找不到该定时任务");
            //如果已经调用过delete，或从来没有进入到调度队列中,resumeJob是无法恢复的，也不抛异常，此时，exists会为false,
            boolean exists = scheduler.checkExists(jobKey);
//            System.out.println("exists = " + exists);
            if (exists) {
                scheduler.resumeJob(jobKey);
                //不存在，有可能是已经delete过，也有可能是从来没有被调度过,需要从库中重新加载并加入调度队列
            } else {
//                //如果对应的触发规则没起用，则给用户告警，先启用规则，因为同一规则下可以有多条任务
//                Integer isEnabled = schedulerTask.getIsEnabled();
//                if (isEnabled == null || isEnabled == 0) {
//                    throw new HlSchedulerException("该任务对应的规则没启用，由于同一规则下可能有多个任务，请先启动对应规则再启用本任务");
//                }
                scheduleTask(schedulerTask, true);
            }
            //先调度成功再入库
            schedulerTask.setIsEnabled(true);
            schedulerTaskDao.saveAndFlush(schedulerTask);

        } catch (SchedulerException e) {
//            log.info("恢复失败，尝试从库中启用一个新的任务,taskId:" + taskId);
            throw new HlSchedulerException("调度一个任务失败,taskId: " + taskId, e);

        }
    }

    /**
     * 立即执行一次job
     *
     * @param taskId
     */
    public void runOneTimeTaskNow(Long taskId) {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        log.info("立即执行一次job,taskId: "+taskId);
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        JobKey jobKey = getJobKey(taskId);
        try {
            //只有调度队列中现成的，才能直接触发一次，否则抛不存在异常
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
//            e.printStackTrace();
            log.info("立即执行一次job失败，尝试从库中启用一个新的任务,taskId: " + taskId);
//            SchedulerTask schedulerTask = schedulerTaskDao.findOne(new SchedulerTask(taskId));
            SchedulerTask schedulerTask = schedulerTaskDao.getOne(taskId);
            HlAssert.notNull(schedulerTask,"找不到该定时任务");
            try {
                scheduleTask(schedulerTask, true);
                //重新从库中入队后立刻暂停
                pauseTask(taskId);
                //然后立刻触发（无规则）
                //只有调度队列中现成的，才能直接触发一次，否则抛不存在异常
                scheduler.triggerJob(jobKey);
            } catch (SchedulerException e1) {
//                e1.printStackTrace();
                throw new HlSchedulerException("从库中启用一个新的任务失败,taskId: " + taskId, e1);
            }
        }
    }


    /**
     * 执行一个任务, 这里的全部group都为taskCode
     *
     * @param schedulerTask
     * @param type          true: 按规则调度执行  false:触发执行一次
     */
    public void scheduleTask(SchedulerTask schedulerTask, boolean type)  {
        Assert.notNull(schedulerFactoryBean, "尚未开启scheduler功能，如果需要，请开启scheduler功能,开启方式: 请设置schedule.enable=true并指定schedule.concurrentJobClass和schedule.unConcurrentJobClass");
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        String taskCode = schedulerTask.getCode();
        //是否并发
        Boolean isConcurrent = schedulerTask.getIsConcurrent();
        isConcurrent = isConcurrent == null ? false : isConcurrent;
        Class clazz = isConcurrent ? concurrentJobClass : unConcurrentJobClass;
        String name = schedulerTask.getBussinessId() + "";
        JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, taskCode).build();
        //在此放值，在对应的job中，通过jobExecutionContext.getMergedJobDataMap()可以获取到值
        jobDetail.getJobDataMap().put(SchedulerJobDataMapConsant.code, taskCode);
        jobDetail.getJobDataMap().put(SchedulerJobDataMapConsant.bussinessId, schedulerTask.getBussinessId());

        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedulerTask.getCronExpression());
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(name, taskCode)
                .withSchedule(scheduleBuilder).build();
        try {
            scheduler.getListenerManager().addJobListener(jobListener);
            if (type) {
                scheduler.scheduleJob(jobDetail, cronTrigger);
            } else {
                scheduler.triggerJob(jobDetail.getKey());
            }
        } catch (SchedulerException e) {
//            e.printStackTrace();
            String message = e.getMessage();
            //如果永远不可能触发
            if (message.contains("will never fire")) {
                HlAssert.message("任务【"+schedulerTask.getName()+"_"+schedulerTask.getCode()+"_["+schedulerTask.getCronExpression()+"]_"+schedulerTask.getId()+"】调度或恢复失败，该定时任务不可能会被触发，建议检查该任务时间");
            }
        }
    }

    private JobKey getJobKey(Long taskId) {
//        SchedulerTask schedulerTask = schedulerTaskDao.findOne(new SchedulerTask(taskId));
        SchedulerTask schedulerTask = schedulerTaskDao.getOne(taskId);
        HlAssert.notNull(schedulerTask,"找不到该定时任务");
        return JobKey.jobKey(schedulerTask.getBussinessId() + "", schedulerTask.getCode());
    }
}
